OneShell

I fight for a brighter tomorrow

0%

[afl-training] quickstart

[afl-training] quickstart

afl-trainning 是之前在安全客上发现的关于 AFL 实战的学习资料,之前一直想通过 AFL 来进行实战,苦于网上的都是一些基础的使用教程,发现这个资料的时候欣喜万分。

在这个 AFL workshop 中,主要包含了以下的一些内容:

  • quickstart:一个简单的例子,通过 afl 编译程序然后使用 afl-fuzz 来 fuzz,新手入门必看,也就是这一篇文章
  • harness:
  • challenges:几个使用 fuzz 可以挖掘出来的经典漏洞
    • libxml2:CVE-2015-8317
    • heartbleed:openssl 的心脏滴血漏洞 CVE-2014-0160
    • sendmail:CVE-1999-0206, CVE-2003-0161
    • ntpq:CVE-2009-0159
    • date:CVE-2017-7476
    • cyber-grand-challenge
    • sendmail/1305

教程是可以使用 docker 的形式创建学习环境的,我这个地方就没有使用了,直接在 git 目录中进行学习。

编译

trainning 中使用的是 AFLplusplus,我此处使用的就是 AFL,因为之前在看 AFL 的源码。

首先进入 quickstart 目录,然后使用 afl-clang 对程序源码进行编译

1
2
cd quickstart
CC=afl-clang AFL_HARDEN=1 make

编译出来的程序是读取 STDIN 标准输入进行处理,可直接运行程序,如果敲下回车不输入数据会显示程序帮助信息,也可以直接从 inputs 提供的种子文件进行运行。

undifined

Fuzzing

使用如下的命令直接进行 fuzz,下图是 fuzz 出来的结果,运行了 44 分钟之后跑出了 9 个 crash

1
afl-fuzz -i inputs -o out ./vulnerable

undifined

问题总结

  • AFL 是如何使用 afl-clang 进行插桩的?

    首先需要知道正常使用 gcc 进行编译和使用 alf-clang 进行编译,产生的可执行文件在二进制上的差别。安全相关,因此先使用 checksec 查看 afl-clang 编译出来的可执行文件开启了哪些防御措施,然后使用 gcc 开启对应的防御参数重新进行编译

undifined

那么使用 gcc 进行编译的命令如下,FORTIFY 选项没有编译出来

1
gcc -no-pie -fstack-protector-all -z noexecstack -O2 -D_FORTIFY_SOURCE=1 -o vulnerable_gcc vulnerable.c

undifined

然后使用 bindiff 工具进行查看,可以看到 afl-clang 编译出来的可执行文件中,在每一个基本块中,都加入了 afl_maybe_log 函数,通过在 AFL 源码目录中搜索该函数,可以定位到是在 afl-as.h 文件中,在源码中是以一串静态字符串形式存储的汇编代码,此处以 64 位为例,源码部分如下。

undifined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static const u8* trampoline_fmt_64 =

"\n"
"/* --- AFL TRAMPOLINE (64-BIT) --- */\n"
"\n"
".align 4\n"
"\n"
"leaq -(128+24)(%%rsp), %%rsp\n"
"movq %%rdx, 0(%%rsp)\n"
"movq %%rcx, 8(%%rsp)\n"
"movq %%rax, 16(%%rsp)\n"
"movq $0x%08x, %%rcx\n"
"call __afl_maybe_log\n"
"movq 16(%%rsp), %%rax\n"
"movq 8(%%rsp), %%rcx\n"
"movq 0(%%rsp), %%rdx\n"
"leaq (128+24)(%%rsp), %%rsp\n"
"\n"
"/* --- END --- */\n"
"\n";

将源代码编译成二进制文件的基本流程是:源代码 -> 汇编代码 -> 二进制代码,将汇编代码编译成二进制的工具就是汇编器 assembler。Linux 常用的汇编器是 as,当完成了 AFL 的编译后,在目录下也会存在一个 as 文件,并且作为符号链接指向 afl-as。因此,此处的代码插桩实现应该是使用的 afl-as,在将源代码编译成汇编代码的过程中,将如上的 afl_maybe_log 函数插入到分支处,也就是在基本块中进行插桩。如上的插桩代码就是 x64 下正常调用一个函数的流程:开辟栈空间,调用 afl_maybe_log 函数,执行完毕函数之后恢复栈平衡。afl_maybe_log 函数也就是插桩具体要执行的内容。此处不多分析 afl_maybe_log 函数的源码,函数位于 afl-as.c 中,就简单说一下函数的实现功能:通过共享内存对基本块的执行情况进行保存。

对于插桩分析得比较不错的可以参考看雪的这篇文章:[原创]AFL编译插桩部分源码分析